//
//  WDFileDownloadOperation.m
//  Pods
//
//  Created by 刘彬 on 2017/3/22.
//
//

#import "WDFileDownloadOperation.h"

// 开始下载通知
NSString *const WDStartDownloadNotification    = @"WDStartDownloadNotification";
// 停止下载通知，包括下载成功，下载失败，取消下载
NSString *const WDStopDownloadNotification     = @"WDStopDownloadNotification";
// 完成下载通知
NSString *const WDFinishDownloadNotification   = @"WDFinishDownloadNotification";
// NSURL KEY
NSString *const WDDownloadURLKey               = @"WDDownloadURLKey";
NSString *const WDDownloadStatusCodeKey        = @"WDDownloadStatusCodeKey";

@interface WDFileDownloadOperation ()

// 下载过程回调
@property (nonatomic, copy) WDFileDownloaderProgressBlock  progressBlock;
// 下载完成回调
@property (nonatomic, copy) WDFileDownloaderCompletedBlock completedBlock;
// 取消回调
@property (nonatomic, copy) WDFileDownloaderCancelBlock    cancelBlock;
// 是否正在下载
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
// 是否已完成
@property (nonatomic, assign, getter=isFinished)  BOOL finished;
// 预计下载文件的size
@property (nonatomic, assign) NSInteger expectedSize;
// 下载的文件data
@property (nonatomic, strong) NSMutableData *fileData;
// 当前下载任务运行的线程
@property (atomic,    strong) NSThread *thread;
// SessionTask的运行session,外部实例化的Session
@property (nonatomic, weak  ) NSURLSession *unownedSession;
// SessionTask的运行session,外部没有创建则内部实例化此Session
@property (nonatomic, strong) NSURLSession *ownedSession;
// 内部实例化的Session
@property (nonatomic, strong) NSURLSessionTask *dataTask;
// 后台任务id
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
// 下载任务执行次数
@property (nonatomic, assign) NSInteger executCount;

@end

@implementation WDFileDownloadOperation

@synthesize executing = _executing;
@synthesize finished  = _finished;

- (instancetype)initWithRequest:(NSURLRequest *)request
                      inSession:(NSURLSession *)session
                       progress:(WDFileDownloaderProgressBlock)progressBlock
                      completed:(WDFileDownloaderCompletedBlock)completedBlock
                      cancelled:(WDFileDownloaderCancelBlock)cancelBlock{
    if (self = [super init]) {
        _request        = request;
        _progressBlock  = [progressBlock copy];
        _completedBlock = [completedBlock copy];
        _cancelBlock    = [cancelBlock copy];
        _executing      = NO;
        _finished       = NO;
        _expectedSize   = 0;
        _unownedSession = session;
    }
    return self;
}

- (void)start {
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }
        self.executCount++;
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        if (self.continueDownloadInBackground) {
            // 程序进入后台继续下载
            __weak __typeof__ (self) wself = self;
            self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;
                if (sself) {
                    [sself cancel];
                    [[UIApplication sharedApplication] endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        NSURLSession *session = self.unownedSession;
        if (!self.unownedSession) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 30;
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
        self.thread = [NSThread currentThread];
    }
    
    [self.dataTask resume];
    
    if (self.dataTask) {
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:WDStartDownloadNotification object:self];
        });
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }
    
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

- (void)cancel {
    @synchronized (self) {
        if (self.thread) {
            [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
        }
        else {
            [self cancelInternal];
        }
    }
}

- (void)cancelInternalAndStop {
    if (self.isFinished) {
        return;
    }
    [self cancelInternal];
}

- (void)cancelInternal {
    if (self.isFinished) {
        return;
    }
    [super cancel];
    if (self.cancelBlock) self.cancelBlock();
    
    if (self.dataTask) {
        [self.dataTask cancel];
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:WDStopDownloadNotification object:self];
        });
        if (self.isExecuting) {
            self.executing = NO;
        }
        if (!self.isFinished) {
            self.finished = YES;
        }
    }
    
    [self reset];
}

- (void)done {
    self.finished = YES;
    self.executing = NO;
    [self reset];
}

- (void)reset {
    self.cancelBlock = nil;
    self.completedBlock = nil;
    self.progressBlock = nil;
    self.dataTask = nil;
    self.thread = nil;
    if (self.ownedSession) {
        [self.ownedSession invalidateAndCancel];
        self.ownedSession = nil;
    }
}

- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (BOOL)isConcurrent {
    return YES;
}

#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    if (![response respondsToSelector:@selector(statusCode)] || [((NSHTTPURLResponse *)response) statusCode] < 400) {
        NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
        self.expectedSize = expected;
        if (self.progressBlock) {
            self.progressBlock(0, expected);
        }
        self.fileData = [[NSMutableData alloc] initWithCapacity:expected];
    } else {
        // 出错重试
        if (self.executCount < self.retryCount+1) {
            NSLog(@"%@ will retry.",self.request.URL.absoluteString);
            [self start];
            return;
        }
        [self.dataTask cancel];
        NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];
        NSDictionary *info = @{NSLocalizedDescriptionKey : @"The download request failed",
                               WDDownloadURLKey : self.request.URL.absoluteString,
                               WDDownloadStatusCodeKey : @(code)};
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:WDStopDownloadNotification object:self];
        });
        
        if (self.completedBlock) {
            self.completedBlock(nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:info], YES);
        }
        [self done];
    }
    
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    [self.fileData appendData:data];
    if (self.progressBlock) {
        self.progressBlock(self.fileData.length, self.expectedSize);
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
    NSCachedURLResponse *cachedResponse = proposedResponse;
    if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
        cachedResponse = nil;
    }
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    if (error) {
        // 出错重试
        if (self.executCount < self.retryCount+1) {
            NSLog(@"%@ will retry.",self.request.URL.absoluteString);
            [self start];
            return;
        }
        if (self.completedBlock) {
            self.completedBlock(nil, error, YES);
        }
    } else {
        WDFileDownloaderCompletedBlock completionBlock = self.completedBlock;
        if (completionBlock) {
            if (self.fileData) {
                completionBlock(self.fileData, nil, YES);
            } else {
                completionBlock(nil, [NSError errorWithDomain:@"WDFileDownloadErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"File data is nil"}], YES);
            }
        }
    }
    
    @synchronized(self) {
        self.thread = nil;
        self.dataTask = nil;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:WDStopDownloadNotification object:self];
            if (!error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:WDFinishDownloadNotification object:self];
            }
        });
    }

    self.completionBlock = nil;
    [self done];
}

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        disposition = NSURLSessionAuthChallengeUseCredential;
    } else {
        if ([challenge previousFailureCount] == 0) {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

@end
